package io.gitee.terralian.code.generator.service.preview.impl;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

import io.gitee.terralian.code.generator.conf.StaticGlobalConf;
import io.gitee.terralian.code.generator.controller.request.PreviewCallRequest;
import io.gitee.terralian.code.generator.dao.entity.DataBase;
import io.gitee.terralian.code.generator.dao.entity.TemplateDef;
import io.gitee.terralian.code.generator.dao.service.DataBaseService;
import io.gitee.terralian.code.generator.dao.service.TemplateDefService;
import io.gitee.terralian.code.generator.service.db.RemoteDBService;
import io.gitee.terralian.code.generator.service.db.entity.ColumnRef;
import io.gitee.terralian.code.generator.service.db.entity.TableRef;
import io.gitee.terralian.code.generator.service.preview.PreviewService;
import io.gitee.terralian.code.generator.service.preview.entity.Preview;
import io.gitee.terralian.code.generator.service.preview.entity.PreviewData;
import io.gitee.terralian.code.generator.service.template.TemplateEngine;
import io.gitee.terralian.code.generator.service.template.entity.DefinedEnum;
import io.gitee.terralian.code.generator.service.template.entity.GenerateConfig;
import io.gitee.terralian.code.generator.service.template.entity.TemplateColumn;
import io.gitee.terralian.code.generator.service.util.UrlHelper;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@AllArgsConstructor
public class PreviewServiceImpl implements PreviewService {

    private final DataBaseService dataBaseService;
    private final RemoteDBService remoteDBService;
    private final TemplateDefService templateDefService;
    private final TemplateEngine templateEngine;

    @Override
    public Preview call(PreviewCallRequest request) {
        Preview preview = new Preview();
        preview.setBaseUrl(request.getBaseUrl());
        setBaseUrlIfNull(preview);
        List<TemplateDef> templateDefs = templateDefService.listByIds(request.getTemplateFileIds());
        DataBase dataBase = dataBaseService.getById(request.getDbId());

        GenerateConfig conf = new GenerateConfig();
        conf.setVariables(request.getParams());
        conf.setVariable(DefinedEnum.baseUrl.name(), preview.getBaseUrl());

        conf.setTableNameReplaceIdentifier(request.getTableNameReplaceIdentifier());

        LocalDateTime now = LocalDateTime.now();
        conf.setVariable(DefinedEnum.date.name(), DateUtil.format(now, DatePattern.NORM_DATETIME_PATTERN));
        conf.setVariable(DefinedEnum.year.name(), now.getYear());
        conf.setVariable(DefinedEnum.month.name(), now.getMonthValue());
        conf.setVariable(DefinedEnum.dayOfMonth.name(), now.getDayOfMonth());
        conf.setVariable(DefinedEnum.hour.name(), now.getHour());
        conf.setVariable(DefinedEnum.minute.name(), now.getMinute());
        conf.setVariable(DefinedEnum.second.name(), now.getSecond());

        List<PreviewData> data = call(conf, templateDefs, dataBase, request.getTableRefs());
        preview.setData(data);

        return preview;
    }

    private List<PreviewData> call(GenerateConfig conf, List<TemplateDef> templateDefs,
            DataBase dataBase,
            List<TableRef> tableRefs) {
        List<PreviewData> dataList = new ArrayList<>();
        for (TableRef tableRef : tableRefs) {
            conf.getAutoImports().clear();
            List<ColumnRef> columnRefs = remoteDBService.getColumns(dataBase, tableRef.getTableName());

            String replacedTableName = replaceTableName(tableRef.getTableName(), conf.getTableNameReplaceIdentifier());
            String entityName = StrUtil.toCamelCase(replacedTableName);
            String EntityName = StrUtil.upperFirst(entityName);
            conf.setVariable(DefinedEnum.tableName.name(), tableRef.getTableName());
            conf.setVariable(DefinedEnum.tableComment.name(), tableRef.getTableComment());
            conf.setVariable(DefinedEnum.entityName.name(), entityName);
            conf.setVariable(DefinedEnum.EntityName.name(), EntityName);
            List<TemplateColumn> columns = parseTemplateColumns(conf, columnRefs);
            conf.setVariable(DefinedEnum.columns.name(), columns);

            conf.setVariable(DefinedEnum.autoImports.name(), conf.getAutoImports());

            for (TemplateDef templateDef : templateDefs) {
                PreviewData data = generateData(conf, templateDef);
                dataList.add(data);
            }
        }
        return dataList;
    }

    private PreviewData generateData(GenerateConfig conf, TemplateDef templateDef) {
        String codeBaseUrl = UrlHelper.format(templateEngine.parse(conf, templateDef.getCodeBaseUrlTemp()));
        conf.setVariable(DefinedEnum.codeBaseUrl.name(), codeBaseUrl);
        String packageName = templateEngine.parse(conf, templateDef.getPackageNameTemp());
        String packageUrl = UrlHelper.toUrl(packageName);
        conf.setVariable(DefinedEnum.packageUrl.name(), packageUrl);
        conf.setVariable(DefinedEnum.packageName.name(), packageName);
        String fileName = templateEngine.parse(conf, StrUtil.blankToDefault(templateDef.getFileNameTemp(),
                "${EntityName}"));
        conf.setVariable(DefinedEnum.fileName.name(), fileName);

        String data = templateEngine.parse(conf, templateDef.getData());

        PreviewData previewData = new PreviewData();
        previewData.setData(data);
        previewData.setCodeBaseUrl(codeBaseUrl);
        previewData.setPackageName(packageName);
        previewData.setPackageUrl(packageUrl);
        previewData.setFileName(fileName);
        setDataExtra(previewData, conf);

        return previewData;
    }

    private List<TemplateColumn> parseTemplateColumns(GenerateConfig config, List<ColumnRef> columnRefs) {
        List<TemplateColumn> columns = new ArrayList<>(columnRefs.size());
        for (ColumnRef columnRef : columnRefs) {
            TemplateColumn column = parseTemplateColumn(config, columnRef);
            columns.add(column);
        }
        return columns;
    }

    private TemplateColumn parseTemplateColumn(GenerateConfig config, ColumnRef columnRef) {
        Map<String, String> javaTypeMap = StaticGlobalConf.javaTypeMap;

        TemplateColumn column = new TemplateColumn();
        column.setName(columnRef.getColumnName());
        column.setNameCamelCase(StrUtil.toCamelCase(columnRef.getColumnName().toLowerCase()));
        column.setNamePascal(StrUtil.upperFirst(column.getNameCamelCase()));
        column.setSqlType(columnRef.getDataType());
        column.setJavaType(javaTypeMap.get(column.getSqlType()));
        column.setIsNullable("YES".equalsIgnoreCase(columnRef.getIsNullable()));
        column.setIsPrimaryKey("PRI".equalsIgnoreCase(columnRef.getColumnKey()));
        column.setOrdinalPosition(columnRef.getOrdinalPosition());
        column.setComment(columnRef.getColumnComment());

        String packageImport = StaticGlobalConf.javaTypePackageMap.get(column.getJavaType());
        if (packageImport != null) {
            config.addPackageImport(packageImport);
        }
        return column;
    }

    private String replaceTableName(String tableName, String tableNameReplaceIdentifier) {
        if (StrUtil.isEmpty(tableNameReplaceIdentifier)) {
            return tableName;
        }
        String[] identifiers = tableNameReplaceIdentifier.split(",");
        return StrUtil.removeAny(tableName, identifiers);
    }

    private void setBaseUrlIfNull(Preview preview) {
        if (StrUtil.isEmpty(preview.getBaseUrl())) {
            String baseUrl = FileUtil.getWebRoot().getPath();
            baseUrl = baseUrl.replaceAll("\\\\", "/");
            baseUrl = baseUrl.replaceAll("//", "/");
            preview.setBaseUrl(baseUrl);
        }
    }

    private void setDataExtra(PreviewData previewData, GenerateConfig conf) {
        StringJoiner builder = new StringJoiner("/");
        builder.add(conf.getStringEmpty(DefinedEnum.baseUrl.name()));
        builder.add(conf.getStringEmpty(DefinedEnum.codeBaseUrl.name()));
        builder.add(conf.getStringEmpty(DefinedEnum.packageUrl.name()));
        previewData.setParentBaseUrl(builder.toString().replaceAll("//", "/"));
        builder.add(conf.getStringEmpty(DefinedEnum.fileName.name()));
        previewData.setAbsoluteUrl(builder.toString().replaceAll("//", "/"));
    }
}
